﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SaunaClient.Model.Logger;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.IO;

namespace SaunaClient.Model.Network
{
    /// <summary>
    /// FTP over Explicit SSL
    /// </summary>
    /// <dates>
    /// 01/09/2011 - created by Chiaki Sato
    /// </dates>
    /// <remarks>
    /// Note:
    /// This class is thread safe. 
    /// </remarks>
    public class FtpDaoGatewayExplicitSsl : FtpBaseSetting, IFtpDaoGateway
    {
        private const string DEFAULT_HOST_ADDRESS = "localhost";
        private const string DEFAULT_USER_NAME = "SaunaFtpUser";
        private const string DEFAULT_PASSWORD_NAME = "pass333";
        private const string DEFAULT_LOCAL_FOLDER = "c:/work/";
        private const string DEFAULT_SERVER_FOLDER = "/";
        private const string FILE_LOGGER = "FileLogger";
        private const string SFTP_LOG_MESSAGE = "Command: {0}";
        private const string FTP_URI_PREFIX = "ftp://";
        private const int BUFFER_SIZE = 1024;
        private const string MESSAGE_FILE_UPLOAD_DONE = "File {0} was successfully uploaded.";
        private const string MESSAGE_FILE_DOWNLOAD_DONE = "File {0} was successfully downloaded.";
        private const string MESSAGE_FILE_LIST_DONE = "Server File list was successfully gotten.";
        private const string MESSAGE_FILELIST_SERVICE_POINT_CONNECTION = "ServicePoint connections = {0}.";
        private const string MESSAGE_FILELIST_CONTENT_LENGTH = "ServicePoint connections = {0}.";
        private const string MESSAGE_FILELIST_STATUS = "List status: {0}";
        private const string MESSAGE_FILELIST_BANNER_MESSAGE = "Banner message: {0}";
        private const string MESSAGE_FILELIST_WELCOME_MESSAGE = "Welcome message: {0}";
        private const string MESSAGE_FILELIST_EXIT_MESSAGE = "Exit message: {0}";

        private object lockObject;

        private LoggerFactoryImplementation loggerFactory;
        private ILogger fileLogger;

        private static FtpDaoGatewayExplicitSsl ftpDaoGatewaySftpSingleton = new FtpDaoGatewayExplicitSsl();

        /// <summary>
        /// Private Constructor for singleton
        /// </summary>
        private FtpDaoGatewayExplicitSsl()
        {
            lockObject = new object();

            HostAddress = DEFAULT_HOST_ADDRESS;
            UserName = DEFAULT_USER_NAME;
            Password = DEFAULT_PASSWORD_NAME;
            ServerDirectory = DEFAULT_SERVER_FOLDER;

            // Create file logger handler
            loggerFactory = new LoggerFactoryImplementation();
            fileLogger = loggerFactory.Make(FILE_LOGGER);
        }

        ///// <summary>
        ///// Get singleton instance
        ///// </summary>
        public static FtpDaoGatewayExplicitSsl Instance(NetworkSetting networkSetting)
        {
            // Initialize Ftp Setting
            ftpDaoGatewaySftpSingleton.HostAddress = networkSetting.FtpHostAddress;
            ftpDaoGatewaySftpSingleton.UserName = networkSetting.FtpUserName;
            ftpDaoGatewaySftpSingleton.Password = networkSetting.FtpPassword;
            ftpDaoGatewaySftpSingleton.ServerDirectory = networkSetting.FtpServerDirectory;
            ftpDaoGatewaySftpSingleton.LocalDirectory = networkSetting.FtpLocalDirectory;
            ftpDaoGatewaySftpSingleton.UseSsl = networkSetting.FtpSsl;
            ftpDaoGatewaySftpSingleton.PortNumber = networkSetting.FtpPortNumber;
            ftpDaoGatewaySftpSingleton.LogFileDirectory = networkSetting.LogFileDirectory;
            ftpDaoGatewaySftpSingleton.loggerFactory = new LoggerFactoryImplementation();
            ftpDaoGatewaySftpSingleton.fileLogger = ftpDaoGatewaySftpSingleton.loggerFactory.Make(FILE_LOGGER);

            return ftpDaoGatewaySftpSingleton;
        }

        /// <summary>
        /// Upload a file
        /// </summary>
        /// <param name="localDirectory">Local directory</param>
        /// <param name="localFileName">File name</param>
        public void Upload(string localDirectory, string localFileName, string serverDirectory)
        {
            // For tread safty
            lock (lockObject)
            {
                // Self Certificate Error Prevention
                ServicePointManager.ServerCertificateValidationCallback =
                    new RemoteCertificateValidationCallback(
                        delegate(Object certsender, X509Certificate certificate, X509Chain chain,
                        SslPolicyErrors sslPolicyErrors) { return true; });

                // File for upload
                string upFile = localDirectory + localFileName;

                // URI to upload
                Uri u = new Uri(FTP_URI_PREFIX + HostAddress + serverDirectory + localFileName);

                // Create FtpWebRequest
                System.Net.FtpWebRequest ftpReq = (System.Net.FtpWebRequest)
                    System.Net.WebRequest.Create(u);
                
                // Ftp username and password
                ftpReq.Credentials = new System.Net.NetworkCredential(UserName, Password);

                // Set Method with WebRequestMethods.Ftp.UploadFile("STOR")
                ftpReq.Method = System.Net.WebRequestMethods.Ftp.UploadFile;
                
                //When request is completed, close connection
                ftpReq.KeepAlive = false;
                
                // ASCII Mode
                ftpReq.UseBinary = false;

                // Disable PASV Mode
                ftpReq.UsePassive = false;

                // Enable SSL
                if (ftpDaoGatewaySftpSingleton.UseSsl)
                {
                    ftpReq.EnableSsl = true;
                }
                else
                {
                    ftpReq.EnableSsl = false;
                }

                // Get stream for upload
                System.IO.Stream reqStrm = ftpReq.GetRequestStream();
                
                // Open the upload file
                System.IO.FileStream fs = new System.IO.FileStream(upFile, System.IO.FileMode.Open, System.IO.FileAccess.Read);
                
                // Write data into stream
                byte[] buffer = new byte[BUFFER_SIZE];
                while (true)
                {
                    int readSize = fs.Read(buffer, 0, buffer.Length);
                    if (readSize == 0)
                        break;
                    reqStrm.Write(buffer, 0, readSize);
                }
                fs.Close();
                reqStrm.Close();

                // Get FtpWebResponse
                System.Net.FtpWebResponse ftpRes =
                    (System.Net.FtpWebResponse)ftpReq.GetResponse();
                // Display status from ftp server
                Console.WriteLine("{0}: {1}", ftpRes.StatusCode, ftpRes.StatusDescription);
                
                // Close
                ftpRes.Close();

                // Log upload
                fileLogger.Write(String.Format(MESSAGE_FILE_UPLOAD_DONE, localFileName));
            }
        }

        /// <summary>
        /// Download a file
        /// </summary>
        /// <param name="serverDirectory">Server directory</param>
        /// <param name="serverFileName">Server file name</param>
        /// <param name="localDirectory">Local directory</param>
        public void Download(string serverDirectory, string serverFileName, string localDirectory)
        {
            // Self Certificate Error Prevention
            ServicePointManager.ServerCertificateValidationCallback =
                new RemoteCertificateValidationCallback(
                    delegate(Object certsender, X509Certificate certificate, X509Chain chain,
                    SslPolicyErrors sslPolicyErrors) { return true; });

            // URI to download
            Uri u = new Uri(FTP_URI_PREFIX + HostAddress + serverDirectory + serverFileName);

            // Local folder to store the downloaded file
            string downFile = localDirectory + serverFileName;

            // Create FtpWebRequest
            System.Net.FtpWebRequest ftpReq = (System.Net.FtpWebRequest)
                System.Net.WebRequest.Create(u);

            // User name and password
            ftpReq.Credentials = new System.Net.NetworkCredential(UserName, Password);
            
            //Set Method with WebRequestMethods.Ftp.DownloadFile("RETR")
            
            ftpReq.Method = System.Net.WebRequestMethods.Ftp.DownloadFile;
            
            // Close connection when request is completed.
            ftpReq.KeepAlive = false;

            // ASCII Mode transfer
            ftpReq.UseBinary = false;

            // Disable PASSIVE mode
            ftpReq.UsePassive = false;

            // Enable SSL
            if (ftpDaoGatewaySftpSingleton.UseSsl)
            {
                ftpReq.EnableSsl = true;
            }
            else
            {
                ftpReq.EnableSsl = false;
            }

            // Get FtpWebResponse
            System.Net.FtpWebResponse ftpRes =
                (System.Net.FtpWebResponse)ftpReq.GetResponse();
            
            // Get Stream to download file
            System.IO.Stream resStrm = ftpRes.GetResponseStream();

            // Create FileStream to write the downloaded file
            System.IO.FileStream fs = new System.IO.FileStream(
                downFile, System.IO.FileMode.Create, System.IO.FileAccess.Write);
            
            // Write data of the downloaded file
            byte[] buffer = new byte[BUFFER_SIZE];
            while (true)
            {
                int readSize = resStrm.Read(buffer, 0, buffer.Length);
                if (readSize == 0)
                    break;
                fs.Write(buffer, 0, readSize);
            }
            fs.Close();
            resStrm.Close();

            // Show status received from FTP server
            Console.WriteLine("{0}: {1}", ftpRes.StatusCode, ftpRes.StatusDescription);
            
            // Close
            ftpRes.Close();

            // Log download
            fileLogger.Write(String.Format(MESSAGE_FILE_DOWNLOAD_DONE, serverFileName));
                
        }

        /// <summary>
        /// Get Server File List
        /// </summary>
        /// <param name="serverDirectory">Server directory</param>
        public String GetServerFileList(string serverDirectory)
        {
            String serverFileList ="";

            // Self Certificate Error Prevention
            ServicePointManager.ServerCertificateValidationCallback =
                new RemoteCertificateValidationCallback(
                    delegate(Object certsender, X509Certificate certificate, X509Chain chain,
                    SslPolicyErrors sslPolicyErrors) { return true; });

            Uri serverUri = new Uri(FTP_URI_PREFIX + HostAddress + serverDirectory);

            // The serverUri should start with the ftp:// scheme.
            if (serverUri.Scheme != Uri.UriSchemeFtp)
            {
                return serverFileList;
            }
            // Get the object used to communicate with the server.
            FtpWebRequest request = (FtpWebRequest)WebRequest.Create(serverUri);

            request.Credentials = new System.Net.NetworkCredential(UserName, Password);

            request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;

            // Enable SSL
            if (ftpDaoGatewaySftpSingleton.UseSsl)
            {
                request.EnableSsl = true;
            }
            else
            {
                request.EnableSsl = false;
            }

            // Debug
            request.KeepAlive = false;
            request.UsePassive = false;

            // Get the ServicePoint object used for this request, and limit it to one connection.
            // In a real-world application you might use the default number of connections (2),
            // or select a value that works best for your application.
            ServicePoint sp = request.ServicePoint;
            Console.WriteLine(MESSAGE_FILELIST_SERVICE_POINT_CONNECTION, sp.ConnectionLimit);
            sp.ConnectionLimit = 1;

            FtpWebResponse response = (FtpWebResponse)request.GetResponse();
            Console.WriteLine(MESSAGE_FILELIST_CONTENT_LENGTH, response.ContentLength);
            // The following streams are used to read the data returned from the server.
            Stream responseStream = null;
            StreamReader readStream = null;
            try
            {
                responseStream = response.GetResponseStream();
                readStream = new StreamReader(responseStream, System.Text.Encoding.UTF8);

                if (readStream != null)
                {
                    // Display the data received from the server.
                    serverFileList = readStream.ReadToEnd();

                    Console.WriteLine(serverFileList);
                }
                Console.WriteLine(MESSAGE_FILELIST_STATUS, response.StatusDescription);
            }
            finally
            {
                if (readStream != null)
                {
                    readStream.Close();
                }
                if (response != null)
                {
                    response.Close();
                }
            }

            Console.WriteLine(MESSAGE_FILELIST_BANNER_MESSAGE, response.BannerMessage);

            Console.WriteLine(MESSAGE_FILELIST_WELCOME_MESSAGE, response.WelcomeMessage);

            Console.WriteLine(MESSAGE_FILELIST_EXIT_MESSAGE, response.ExitMessage);

            // Log getting server file list
            fileLogger.Write(MESSAGE_FILE_LIST_DONE);

            return serverFileList;
        }
    }
}
